#include "syncmanager.h"

SyncManager::SyncManager() {
	SYNC = "sync";
	ACTION = "action";
	COMPLETE = "complete";
	FINISH = "finish";
	ACTION_SYNC = "sync";
	ACTION_ADD = "add";
	ACTION_DELETE = "delete";
	ACTION_REPLACE = "replace";
	ACTION_CALL = "call";
	ACTION_STOP_CALL = "stopcall";
	homeDir = "Documents";

	server = new QTcpServer();
	connect(server, SIGNAL(newConnection()),
            this, SLOT(connectionServlet()));
	client = NULL;
	socket = NULL;
		
}

SyncManager::~SyncManager() {
	qDebug() << "SyncManager::~SyncManager() |";
}

void SyncManager::closeConnection() {
	qDebug() << "SyncManager::closeConnection() |";
	if (client != NULL) {
		client->disconnectFromHost();
		client = NULL;
	}
	if (socket != NULL) {
		socket->disconnectFromHost();
		socket = NULL;
	}
}

int SyncManager::listen(QString ipStr, QString portStr) {
	qDebug() << "SyncManager::listen() |";
	QHostAddress addr(ipStr);
	uint port = portStr.toUInt();
	if(server->listen(addr, port)) {
		emit statusSignal(LISTEN);
	} else {
		emit statusSignal(ERROR_ADDRESS);
	}	
	return 0;
}

int SyncManager::stop() {
	qDebug() << "SyncManager::stop() |";
	server->close();
	emit statusSignal(STOP);
	return STOP;
}
QString SyncManager::getClientIp() {
	QString ip = "";
	if (client != NULL) ip = client->peerAddress().toString();
	return ip;
}
QString SyncManager::getClientPort() {
	QString port = "";
	if (client != NULL) port += client->peerPort();
	return port;
}
void SyncManager::connectionServlet() {
    client = server->nextPendingConnection();
	qDebug() << "Connect from " << client->peerAddress().toString()
			<< client->peerPort();
    connect(client, SIGNAL(disconnected()), this, SLOT(disconnectionServlet()));
	connect(client, SIGNAL(error(QAbstractSocket::SocketError)),
			this, SLOT(errorHandler(QAbstractSocket::SocketError)));
	emit statusSignal(CONNECT);
	setupServlet(DISPATCHER);
	//emptyHome();
}
void SyncManager::emptyHome() {
	QDir dir = QDir::home();
	dir.cd("Documents_PC");
	QStringList list = dir.entryList();
	for (int i=0; i<list.size(); i++) {
		QString filename = list.at(i);
		QFile file(dir.filePath(filename));
		QFileInfo info(file);
		if (info.isFile()) {
			file.remove();
		}
	}
}
void SyncManager::disconnectionServlet() {
	qDebug() << "SyncManager::disconnectionServlet |";
	qDebug() << "Disconnect from " << client->peerAddress().toString()
			<< client->peerPort();
	closeConnection();
}
void SyncManager::dispatcherServlet() {
	qDebug() << "SyncManager::dispatcherServlet() |";
    QByteArray data = client->read(sizeToRead);
	qDebug() << "Need" << sizeToRead << "bytes, read" << data.size() << "bytes";
	//===== Got data to process
	if (data.size() != 0) {
		message += data;
		sizeToRead -= data.size();
		if (sizeToRead == 0) {
			qDebug() << "Message completed.";
			messageDispatcher(message);
		} else {
			qDebug() << "Waiting" << sizeToRead << "bytes message data.";
		}
	}
}
void SyncManager::messageDispatcher(QString message) {
	qDebug() << "SyncManager::messageDispatcher() | " << message;

	QString header = message.section(":", 0, 0);
	QString msg = message.section(":", 1);
	qDebug() << "Header:" << header;
	qDebug() << "Message:" << msg;
	//===== Sync
	if (header.compare(SYNC) == 0) {
		setupFileUpload(msg);
	//===== Action
	} else if (header.compare(ACTION) == 0) {
		processAction(msg);
		setupServlet(DISPATCHER);
	//===== Undefine
	} else {
		qDebug() << "Undefined header:" << header;
		setupServlet(DISPATCHER);
	}
}

void SyncManager::processAction(QString action) {
	qDebug() << "SyncManager::processAction()" << action;
	QString type = action.section(":", 0, 0);
	QString payload = action.section(":", 1);
	qDebug() << "Type:" << type;
	qDebug() << "Detail:" << payload;

	//===== Sync done (sync:[finish|complete]:)
	if (type.compare(ACTION_SYNC) == 0) {
		payload = payload.section(":", 0, 0);
		if (payload.compare(FINISH) == 0) {
			//sendAction(SYNC, COMPLETE);
			processServerSync();			
		} else if (payload.compare(COMPLETE) == 0) {
			emit statusSignal(SYNCED); // for client
		}

	//===== Add contact (add:entry:)
	} else if (type.compare(ACTION_ADD) == 0) {
		payload = payload.section(":", 0, 0);
		qDebug() << "Add contact" << payload;
		NeoPhoneBookEntry *entry = new NeoPhoneBookEntry();		
		if (entry->setPhoneBookEntry(payload)) {
			emit addContactSignal(entry);
		} else {
			qDebug() << "Invalid phone book entry" << payload;
		}

	//===== Delete contact (delete:index:)
	} else if (type.compare(ACTION_DELETE) == 0) {
		payload = payload.section(":", 0, 0);
		qDebug() << "Delete contact" << payload;
		int index = parseInt(payload);
		if (index < 0) {
			qDebug() << "Invalid index" << payload;
		} else {
			emit deleteContactSignal(index);
		}

	//===== Replace contact (replace:index:entry:)
	} else if (type.compare(ACTION_REPLACE) == 0) {
		QString indexStr = payload.section(":", 0, 0);
		QString entryStr = payload.section(":", 1, 1);
		qDebug() << "Replace contact" << indexStr << "@" << entryStr;
		int index = parseInt(indexStr);
		NeoPhoneBookEntry *entry = new NeoPhoneBookEntry();	
		if (index>=0 && entry->setPhoneBookEntry(entryStr)) {
			emit replaceContactSignal(index, entry);
		} else {
			qDebug() << "Invalid index or entry";
		}

	//===== Call contact (call:index:)
	} else if (type.compare(ACTION_CALL) == 0) {
		payload = payload.section(":", 0, 0);
		qDebug() << "Call contact" << payload;
		int index = parseInt(payload);
		if (index < 0) {
			qDebug() << "Invalid index" << payload;
		} else {
			emit callContactSignal(index);
		}

	//===== Stop call contact (stopcall::)
	} else if (type.compare(ACTION_STOP_CALL) == 0) {		
		qDebug() << "Stop call contact";
		emit stopCallContactSignal();
		
	//===== Undefined
	} else {
		qDebug() << "Undefined action" << type;
	}	
}

int SyncManager::parseInt(QString indexStr) {
	bool ok;
	int index = indexStr.toInt(&ok, 10);
	if (ok) {
		return index;
	} else {
		return -1;
	}
}

void SyncManager::sendAction(QString type, QString payload) {
	qDebug() << "SyncManager::sendAction" << type << payload;
	QString message = ACTION + ":" + type + ":" + payload + ":";
	sendMessage(message);
}

void SyncManager::setupFileUpload(QString message) {
	qDebug() << "SyncManager::setupFileUpload() |" << message;
	filename = message.section(":", 0, 0);
	sizeToRead = message.section(":", 1, 1).toInt();
	qDebug() << "Prepare for uploading file" << filename 
			<< "["<<sizeToRead<<"bytes]";
	QDir dir = QDir::home();
	dir.cd(homeDir);
	QString filepath = dir.path() + "/" + filename;
	file = new QFile(filepath);	
	if (!file->open(QIODevice::WriteOnly)) {
	    qDebug() << "Can't open file" << filename << "to write.";
	    return;
	}	
	setupServlet(FILE_UPLOAD);
}

/**
 * @ensure file(global var) is ready to write to.
 * @ensure filename(global var) is correct.
 * @ensure filesize(global var) is correct.
 */
void SyncManager::fileUploadServlet() {
	qDebug() << "SyncManager::fileUploadServlet |";

	QByteArray data = client->read(sizeToRead);
	qDebug() << "Need" << sizeToRead << "bytes, read" << data.size() << "bytes";
	//===== got data to write OR file contains no data
	if (data.size() != 0 || sizeToRead == 0) {
		int count = file->write(data);
		sizeToRead -= count;
		qDebug() << "Write" << count << "bytes to file" << filename;
		if (sizeToRead == 0) {
			file->close();
			qDebug() << "Upload completed.";
			setupServlet(DISPATCHER);
		} else {
			qDebug() << sizeToRead << "bytes remains.";
		}
	}
}

void SyncManager::setupServlet(int servlet) {
	qDebug() << "SyncManager::setupServlet() |" << servlet;
	//===== disconnect signal
	if (client != NULL) {
		client->disconnect(SIGNAL(readyRead()));
		//disconnect(client, SIGNAL(readyRead()), this, SLOT(dispatcherServlet()));
		//disconnect(client, SIGNAL(readyRead()), this, SLOT(fileUploadServlet()));
	}
	switch (servlet) {
	case DISPATCHER:
		qDebug() << "Setting up for dispatcher servlet...";
		sizeToRead = MESSAGE_SIZE;
		message = "";
		connect(client, SIGNAL(readyRead()), this, SLOT(dispatcherServlet()));
		if (client != NULL && (client->bytesAvailable()) != 0) {
			dispatcherServlet();
		}
		break;
	case FILE_UPLOAD:
		qDebug() << "Setting up for file upload servlet...";	
		connect(client, SIGNAL(readyRead()), this, SLOT(fileUploadServlet()));
		if (client != NULL && (client->bytesAvailable()) != 0) {
			fileUploadServlet();
		}
		break;
	default:
		qDebug() << "Unsupport servlet" << servlet;
	}
}

void SyncManager::sendMessage(QString msg) {	
	qDebug() << "Sending message: " << msg;
	QByteArray data = msg.toAscii();
	data.resize(MESSAGE_SIZE);
	if (client !=NULL && client->state() == QAbstractSocket::ConnectedState) {
		int count = client->write(data);
		client->flush();
		qDebug() << count << "bytes sent";
	} else {
		qDebug() << "Client not connected. Message abort.";
	}
}

void SyncManager::errorHandler(QAbstractSocket::SocketError error) {
	qDebug() << "SyncManager::errorHandler() |" << error << client->errorString();
	emit statusSignal(ERROR_NETWORK);
	closeConnection();
}

//=======================Phone==================================
void SyncManager::sync(QString ipStr, QString portStr) {
	if (validateAddress(ipStr, portStr)) {
		QHostAddress addr(ipStr);
		uint port = portStr.toUInt();
		client = new QTcpSocket();
		connect(client, SIGNAL(connected()), this, SLOT(processSync()));
		setupServlet(DISPATCHER);
		connect(client, SIGNAL(error(QAbstractSocket::SocketError)),
			this, SLOT(errorHandler(QAbstractSocket::SocketError)));
		client->connectToHost(addr, port);
	} else {
		emit statusSignal(ERROR_ADDRESS);
	}
}
bool SyncManager::validateAddress(QString ipStr, QString portStr) {
	bool ipOk = false;
	bool portOk = false;
	QHostAddress address;
	if (address.setAddress(ipStr)) ipOk = true;
	bool ok;
	uint port = portStr.toUInt(&ok, 10);
	if (ok) {
		if (port > 1024 && port < 65536) portOk = true;
	}
	if (ipOk && portOk) return true;
	return false;
}

void SyncManager::sendFile(QString filename) {
	qDebug() << "SyncManager::sendFile() |" << filename;
	QDir dir = QDir::home();
	dir.cd(homeDir);
	QFile file(dir.filePath(filename));
	QFileInfo info(file);
	if (info.isFile()) {
		if (!file.open(QIODevice::ReadOnly)) {
			qDebug() << "Can't open file";
			return;
		} else {
			QString msg = SYNC + ":" + filename + ":" + QString::number(file.size()) 
					+ ":";
			sendMessage(msg);
			int count = client->write(file.readAll());
			client->flush();
			file.close();
			qDebug() << "Send file" << filename << "[" << file.size() << "]";
			qDebug() << count << "bytes sent";
		}
	} else {
		qDebug() << "Skip non-file" << filename;
	}
}
void SyncManager::processSync() {
	emit statusSignal(CONNECT);
	QDir dir = QDir::home();
	dir.cd("Documents");
	QStringList list = dir.entryList();
	for (int i=0; i<list.size(); i++) {
		QString filename = list.at(i);
		sendFile(filename);
	}
	sendAction(ACTION_SYNC, FINISH);
}

void SyncManager::addContactListener(NeoPhoneBookEntry *entry) {
	sendAction(ACTION_ADD, entry->toString());
}
void SyncManager::replaceContactListener(int contactIndex, NeoPhoneBookEntry *entry) {
	sendAction(ACTION_REPLACE, QString::number(contactIndex) + ":" + entry->toString());
}
void SyncManager::deleteContactListener(int contactIndex) {
	sendAction(ACTION_DELETE, QString::number(contactIndex));
}
void SyncManager::callContactListener(int contactIndex) {
	sendAction(ACTION_CALL, QString::number(contactIndex));
}
void SyncManager::stopCallContactListener() {
	sendAction(ACTION_STOP_CALL, "");
}

bool SyncManager::validateIp(QString ipStr) {	
	QHostAddress address;
	return address.setAddress(ipStr);	
}

bool SyncManager::validatePort(QString portStr) {
	bool ok;
	uint port = portStr.toUInt(&ok, 10);
	return ok && port > 1024 && port < 65536;
}
void SyncManager::processServerSync() {
	qDebug() << "SyncManager::processServerSync() |";
	QDir dir = QDir::home();
	dir.cd("Documents_PC");
	QStringList list = dir.entryList();
	for (int i=0; i<list.size(); i++) {
		QString filename = list.at(i);
		if (filename.compare("log") != 0) {
			qDebug()<<"call sendFile("<<filename<<")";
			sendFile(filename);
		}
	}
	sendAction(SYNC, COMPLETE);
	emit statusSignal(SYNCED); // for server
}
void SyncManager::setHomeDir(QString homeDir) {
	this->homeDir = homeDir;
}



